1 /*
2  * Hunt - a framework for web and console application based on Collie using Dlang development
3  *
4  * Copyright (C) 2015-2017  Shanghai Putao Technology Co., Ltd
5  *
6  * Developer: HuntLabs
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11  
12 module hunt.application.controller;
13 
14 import kiss.logger;
15 
16 public import hunt.view;
17 public import hunt.http.response;
18 public import hunt.http.request;
19 public import hunt.routing;
20 public import hunt.application.middleware;
21 
22 import hunt.cache;
23 import hunt.application.application;
24 
25 import std.exception;
26 import std.traits;
27 import std.string;
28 
29 enum Action;
30 
31 struct Middleware
32 {
33     string className;
34 }
35 
36 
37 abstract class Controller
38 {
39     protected
40     {
41         Request request;
42         ///called before all actions
43         IMiddleware[] middlewares;
44         View _view;
45     }
46     final @property session()
47     {
48         return request.getSession();
49     }
50     final @property response()
51     {
52         return request.createResponse();
53     }
54     /// called before action  return true is continue false is finish
55     // bool before(){return true;}
56     bool before()
57 	{
58 		/**
59 		CORS support
60 		http://www.cnblogs.com/feihong84/p/5678895.html
61 		https://stackoverflow.com/questions/10093053/add-header-in-ajax-request-with-jquery
62 		*/
63 
64         // FIXME: Needing refactor or cleanup -@zxp at 5/10/2018, 11:33:11 AM
65         // set this through the configuration
66 		response.setHeader("Access-Control-Allow-Origin", "*");
67 		response.setHeader("Access-Control-Allow-Methods", "*");
68 		response.setHeader("Access-Control-Allow-Headers", "*");
69 
70 		if (toUpper(request.method) == "OPTIONS")
71 		{
72 			return false;
73 		}
74 			
75 		return true;
76 	}
77 
78     /// called after action  return true is continue false is finish
79     bool after(){return true;}
80 
81     ///add middleware
82     ///return true is ok, the named middleware is already exist return false
83     bool addMiddleware(IMiddleware m)
84     {
85         if(m is null) return false;
86         foreach(tmp; this.middlewares)
87         {
88             if(tmp.name == m.name)
89             {
90                 return false;
91             }
92         }
93 
94         this.middlewares ~= m;
95         return true;
96     }
97 
98     // get all middleware
99     IMiddleware[] getMiddlewares()
100     {
101         return this.middlewares;
102     }
103 
104     //view render
105     @property View view()
106     {
107         if(_view is null)
108         {
109             _view = new View();
110         }
111         return _view;
112     }
113 
114     @property UCache cache()
115     {
116 		return Application.getInstance().getCache();
117     }
118 
119 	@property cacheManger()
120 	{
121 		return Application.getInstance().getCacheManger();
122 	}
123 
124     void render(string filename = null)()
125     {
126         this.response.html(this.view.render!filename());
127     }
128 	alias show = render;
129 
130     void done()
131     {
132         this.response.done();
133     }
134 
135     protected final bool doMiddleware()
136     {
137         foreach(m; middlewares)
138         {
139            logDebugf("do %s onProcess ..", m.name());
140 
141             auto response = m.onProcess(this.request, this.response);
142             if(response is null)
143             {
144                 continue;
145             }
146 
147            logDebugf("Middleware %s is retrun done.", m.name);
148             response.done();
149             return false;
150         }
151 
152         return true;
153     }
154 
155 	@property bool isAsync()
156 	{
157 		return true;
158 	}
159 }
160 
161 mixin template MakeController(string moduleName = __MODULE__)
162 {
163     mixin HuntDynamicCallFun!(typeof(this),moduleName);
164 }
165 
166 mixin template HuntDynamicCallFun(T,string moduleName)
167 {
168 public:
169     mixin(__createCallActionFun!(T,moduleName));
170     shared static this()
171     {
172         mixin(__creteRouteMap!(T,moduleName));
173     }
174 }
175 
176 string  __createCallActionFun(T, string moduleName)()
177 {
178     import std.traits;
179     import std.format;
180 
181     string str = "bool callAction(string funName, Request req) {";
182     str ~= "\n\tauto ptr = this; ptr.request = req;";
183     str ~= "\n\tswitch(funName){";
184     foreach(memberName; __traits(allMembers, T))
185     {
186         static if (is(typeof(__traits(getMember,  T, memberName)) == function) )
187         {
188             foreach (t;__traits(getOverloads,T,memberName)) 
189             {
190                 //alias pars = ParameterTypeTuple!(t);
191                 static if(/*ParameterTypeTuple!(t).length == 0 && */( hasUDA!(t, Action) || hasUDA!(t, Route)))
192                 {
193                     str ~= "case \"";
194                     str ~= memberName;
195                     str ~= "\": {\n";
196                     static if(hasUDA!(t, Action))
197                     {
198                         enum middlewares = getUDAs!(t, Middleware);
199                         static if(middlewares.length)
200                         {
201                             foreach(i, middleware; middlewares)
202                             {
203                                 str ~= format("ptr.addMiddleware(new %s);", middleware.className);
204                             }
205                         }
206 
207                         str ~= "if(!ptr.doMiddleware()){return false;}";
208 
209                         //before
210                         str ~= q{
211                             if(!ptr.before()){return false;}
212                         };
213                     }
214 
215                     //action
216                     str ~= "ptr." ~ memberName ~ "();";
217 
218                     static if(hasUDA!(t, Action)){
219                         //after
220                         str ~= q{
221                             if(!ptr.after()){return false;}
222                         };
223                     }
224                     str ~= "}\n break;";
225                 }
226             }
227         }
228     }
229 
230     str ~= "default : break;}";
231     str ~= "return false;";
232     str ~= "}";
233 
234     return str;
235 }
236 
237 string  __creteRouteMap(T, string moduleName)()
238 {
239     string str = "";
240 
241     //pragma(msg, "moduleName", moduleName);
242     str ~= "\n\timport hunt.application.staticfile;\n";
243     str ~= "\n\taddRouteList(\"hunt.application.staticfile.StaticfileController.doStaticFile\", &callHandler!(StaticfileController, \"doStaticFile\"));\n";
244     
245     foreach(memberName; __traits(allMembers, T))
246     {
247         static if (is(typeof(__traits(getMember, T, memberName)) == function))
248         {
249             foreach (t;__traits(getOverloads, T, memberName))
250             {
251                 static if (/*ParameterTypeTuple!(t).length == 0 && */ hasUDA!(t, Action))
252                 {
253                     str ~= "\n\taddRouteList(\"" ~ moduleName ~ "." ~ T.stringof ~ "." ~ memberName  ~ "\",&callHandler!(" ~ T.stringof ~ ",\"" ~ memberName ~ "\"));\n";
254                 }
255             }
256         }
257     }
258 
259     return str;
260 }
261 
262 void callHandler(T, string fun)(Request req) if(is(T == class) || is(T == struct) && hasMember!(T,"__CALLACTION__"))
263 {
264     T handler = new T();
265     
266 	import core.memory;
267 	scope(exit){if(!handler.isAsync){handler.destroy(); GC.free(cast(void *)handler);}}
268 
269     //handler.before();		// It's already been called in line 183.
270     req.action = fun;
271     handler.callAction(fun, req);
272     handler.after();		// Although the line 193 also has the code that calls after, but where has not executed, so this reservation
273     handler.done();
274 }
275 
276 HandleFunction getRouteFormList(string str)
277 {
278     if (!_init)
279     {
280         _init = true;
281     }
282 
283     return __routerList.get(str, null);
284 }
285 
286 void addRouteList(string str, HandleFunction fun)
287 {
288     //trace("add str is .... ", str);
289     if(!_init)
290     {
291         import std.string : toLower;
292 
293         __routerList[str.toLower] = fun;
294     }
295 }
296 
297 private:
298 __gshared bool _init = false;
299 __gshared HandleFunction[string]  __routerList;